﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace Elca.MvvmHelpers {

    /// <summary>
    /// Allows binding a command to the Return key or Esc key.
    /// </summary>
    public class PreviewKeyHelper {

        private readonly static List<FrameworkElement> s_knownElements = new List<FrameworkElement>();

        public static readonly DependencyProperty CarriageReturnCommandProperty =
                DependencyProperty.RegisterAttached(
                        "CarriageReturnCommand", typeof (ICommand), typeof (PreviewKeyHelper),
                        new UIPropertyMetadata(OnIsCommandPropertyChanged));


        public static ICommand GetCarriageReturnCommand(FrameworkElement obj) {
            return (ICommand)obj.GetValue(CarriageReturnCommandProperty);
        }

        public static void SetCarriageReturnCommand(FrameworkElement obj, ICommand value) {
            obj.SetValue(CarriageReturnCommandProperty, value);
        }

        public static readonly DependencyProperty EscapeCommandProperty =
                DependencyProperty.RegisterAttached(
                        "EscapeCommand", typeof (ICommand), typeof (PreviewKeyHelper),
                        new UIPropertyMetadata(OnIsCommandPropertyChanged));


        public static ICommand GetEscapeCommand(FrameworkElement obj) {
            return (ICommand)obj.GetValue(EscapeCommandProperty);
        }

        public static void SetEscapeCommand(FrameworkElement obj, ICommand value) {
            obj.SetValue(EscapeCommandProperty, value);
        }

        private static void OnIsCommandPropertyChanged(DependencyObject d,
                                                       DependencyPropertyChangedEventArgs e) {
            var fe = (FrameworkElement) d;

            if (! s_knownElements.Contains(fe)) {
                HookHandlers(fe);
                s_knownElements.Add(fe);
            }
        }

        private static void HookHandlers(FrameworkElement fe) {
            fe.Unloaded += OnUnloaded;
            fe.PreviewKeyDown += OnPreviewKeyDown;
        }

        private static void OnPreviewKeyDown(object sender, KeyEventArgs e) {

            FrameworkElement fe = (FrameworkElement)sender;
            ICommand crCommand = GetCarriageReturnCommand(fe);
            if (crCommand != null) {
                if (e.Key == Key.Enter || e.Key == Key.Return) {
                    if (crCommand.CanExecute(null)) {
                        crCommand.Execute(null);
                        e.Handled = true;
                    }
                }
            }

            if (! e.Handled) {

                ICommand escCommand = GetEscapeCommand(fe);
                if (escCommand != null) {
                    if (e.Key == Key.Escape) {
                        if (escCommand.CanExecute(null)) {
                            escCommand.Execute(null);
                            // 1.10 no longer cancels the key for upper layers
                            //e.Handled = true;
                        }
                    }
                }

            }
        }


        private static void OnUnloaded(object sender, RoutedEventArgs e) {
            FrameworkElement fe = (FrameworkElement)sender;
            UnhookHandlers(fe);
            s_knownElements.Remove(fe);
        }

        private static void UnhookHandlers(FrameworkElement fe) {
            fe.Unloaded -= OnUnloaded;
            fe.PreviewKeyDown -= OnPreviewKeyDown;
        }
    }
}
